Skip to content

Conversation

@perminder-17
Copy link
Collaborator

@perminder-17 perminder-17 commented Oct 26, 2025

Resolves #8156

Changes:

  • The no-argument usage of createVector() to be marked for deprecation.
  • FES to suggest not using createVector without arguments
  • Prevent NaN from appearing when a createVector() is subject to any operations.
  • Add unit tests and improve documentation.

Screenshots of the change:

PR Checklist

@perminder-17 perminder-17 linked an issue Oct 26, 2025 that may be closed by this pull request
4 tasks
@perminder-17 perminder-17 marked this pull request as draft October 26, 2025 15:58
@perminder-17 perminder-17 marked this pull request as ready for review October 26, 2025 20:07
const matrix = this._renderer.getWorldToScreenMatrix();

if (screenPosition.dimensions === 2) {
if (origDimension === 2 || screenToWorldArgs === 2) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic here is, when we are padding 0's to the other components of vector to prevent NaN, the screenToWorld breaks because it the count here changes and maybe it projects the z components as well.

So, when we see screenPosition.dimensions always comes out to 3 since we are storing other component with 0. Here I am preserving the count with original arguments and makes the test pass. Since it's not the actual fix and tends to be a workaround for the future release, I think this should work, Let me know if there's any objection @davepagurek

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me know if I'm understanding the flow here correctly:

  • If you pass two numbers into screenToWorld (e.g. screenToWorld(width/2, height/2)) or if you pass a single vector in that was created with two numbers (e.g. screenToWorld(createVector(1, 2))), we want to generate a z value
  • We can directly check the arguments length, but when the arg is a single vector, its values get padded to length 3, so we don't know directly how many arguments were manually provided
  • We store a global dimension value corresponding to the number of args passed to the last createVector call

That sounds like it works. I wonder if it might be a little more direct, instead of using global state, to edit createVector to instead set a secret internal property on the vector before returning it, like _origDimension, so then we could check if that exists on any vector?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you are right. I am using _origDimension now, if screenToWorld(width/2, height/2) we take the argument from there, and if we have not a number (probably a vector) we fetch the _origDimension, other logic are same? Also, now we are not using global state. Do you think it's correct for now?

@GregStanton
Copy link
Collaborator

GregStanton commented Oct 28, 2025

Hi @perminder-17 and @davepagurek!

Thanks so much for working on this. I'm really glad we're trying to figure out how to accommodate the 1.x createVector() usage.

Since I've been organizing the p5.Vector stabilization work (#8149), I wanted to check in on this PR to see how it would interact with the other open issues. I took a quick look at the implementation, and I'm concerned that the current approach—padding all 1D/2D vectors to 3D—is much broader than the "targeted patch" that was proposed and may have major, unintended side effects.

This implementation seems to revert p5.Vector to the 1.x model, which prevents 2.x from having true 1D or 2D vectors. This would unfortunately block or break most of the community's other stabilization work.

Here are the key conflicts I've identified:

  1. Breaks current 2.x behavior: This PR would break valid 2.x features. For example, toString() on a 2D vector currently (and correctly) returns two components. This change would either break the toString() contract or create a confusing state where users don't know whether to trust vec.z or toString(). (See #8153.)

  2. Blocks the broadcasting policy: This change would make the widely-supported broadcasting policy (#8159) impossible to implement. That policy requires us to distinguish 2D and 3D vectors to throw correct errors (e.g., on vec2.add(vec3)). If all vectors are 3D or higher, we lose this ability, which means more complex documentation for features like add(), sub(), mult(), div(), and rem().

  3. Blocks other core fixes: The simple fixes for cross() (#8210), heading() (#8214), and setHeading() (#8215) all depend on being able to distinguish true 2D vectors from 3D vectors. This PR would prevent those fixes.

  4. Violates user expectations: This violates the principle of least astonishment. In 2.x, a user writing createVector(2, 3) correctly expects the vector's components to be [2, 3] (as currently reported by toString()), not [2, 3, 0]. Finding that vec.z is 0 or that vec.toString() reports three components would confuse them.

  5. Propagates 1.x complexity: This padding logic forces other functions like set() to retain confusing side effects from 1.x (e.g., v.set(1, 1) on [1.01, 1.01, 5] becoming [1, 1, 0]). This blocks the simpler, powerful broadcasting solution we've been working on in #8189.

A Real-Time Example of This Complexity

This PR's discussion of the screenToWorld bug is a perfect example of this complexity in action.

To fix the bug this patch created, a new "secret internal" _origDimension property was needed to track the intended state versus the actual (padded) state. This forced a kind of kludge, which creates technical debt: It's brittle and will likely break if vector internals are refactored, since it relies on the vector holding conflicting state (its real shape and its intended shape). This is the exact kind of complexity that the 2.x stabilization work (like a stable, user-facing .shape property in #8155) is designed to solve, but the current PR blocks that work (the shape property is most viable if the vector has a single, unambiguous shape).

Summary

This PR would significantly complicate the entire vector class, for all 2.x users, in order to accommodate a legacy edge case in a single function.

Fundamentally, this patch replaces the simpler, more robust 2.x model with the legacy 1.x model. This breaks or blocks improvements that have already been added or are actively being worked on, based on extensive discussions (including the concept of true 1D and 2D vectors, as discussed in #8118).

Proposal: Move this back to #8156 for alternatives

Since this patch appears to have a much larger blast radius than intended, I propose we continue to discuss viable options in #8156, before we merge. I think we need to find a way to patch the createVector() no-argument case without undermining the entire n-dimensional model.

My sense is that the simplest, lowest-impact solution may be to include a short note in the compatibility README, instructing users how to fix their sketches by supplying explicit 0 arguments to createVector(). But I think the wider community should have a chance to weigh in.

Thanks again for all your work on this!

@davepagurek
Copy link
Contributor

This change would make the widely-supported broadcasting policy (#8159) impossible to implement.

I think we aren't trying to reach a permanent solution in this, but just unblock sketches that are currently broken. So it's very likely that we undo whatever we do in this PR when making larger changes to broadcasting.

That said, do you think it would be reasonable to do a similar kind of padding, but only when operating on another vector for now? So like, temporarily implementing a version of broadcasting where we pad with 0s, but only when you add two vectors. So if you did:

const a = createVector(1, 2) // Dimension is 2
const b = createVector(3, 4, 5) // Dimension is 3
a.add(b) // Maybe this logs an FES message saying you've multiplied two different-sized vectors together
// A's dimension is now 3, its data is [4, 6, 5]

This would make dimension and toString() work correctly at first, and would stop NaNs from appearing in data right now. It isn't the full solution to broadcasting -- essentially it's using one form of broadcasting as a stopgap until we work out a full solution. But I think that takes some of what's currently broken and leaves it in a less broken state without introducing new breakages?

@GregStanton
Copy link
Collaborator

GregStanton commented Oct 29, 2025

Hi @davepagurek! Thank you so much for your quick reply. I love seeing all these ideas being put forth, and your new idea is really interesting.

We're totally in agreement about wanting to fix the current user-facing bugs as quickly as possible. With that in mind, I have a proposal for a different quick fix, a few concerns about the "stopgap" approach, and what I see as a path to immediately unlock quick, permanent fixes.

A truly quick fix

If our goal is to unblock users today with the lowest possible effort and zero technical debt, I think the fastest path is to add a note to the compatibility README and the 2.0 beta documentation. Something like this:

"In 1.x, createVector() was often used as a shortcut for createVector(0, 0, 0). In 2.x, createVector() with no arguments is no longer supported, as we now have true 1D/2D vectors. If your 1.x sketch breaks when upgrading to 2.x, you can fix it by explicitly using createVector(0), createVector(0, 0), or createVector(0, 0, 0), depending on the desired dimension."

This is a 10-minute, no-code change that immediately solves the user's "why is my sketch broken?" problem. Crucially, we can reinforce this with a dedicated Friendly Error System message. That way, the README and reference pages provide advance notice, and users who are still bitten by the bug will find an immediate, simple solution when and where it happens.

Risks of a custom "stopgap"

My main concern with the stopgap you proposed (padding with 0s for add()) is that it's not a partial form of standard broadcasting—it's a new, custom rule that explicitly violates the universally adopted broadcasting rule.

This rule is precisely the same in every major library for math, machine learning, and when applicable, creative-coding. For example, openFrameworks also follows the same rule.

Violating the rule means we'd have to code in warning messages that teach users special behavior that they'll have to unlearn—the permanent solution explicitly disallows this exact behavior. This feels like the "whack-a-mole" problem: we'd be solving the NaN bug by introducing a new source of user confusion.

An opportunity for quick, permanent fixes

This brings me to what I think is the most exciting and high-leverage opportunity.

The permanent solutions for this bug (and many others) are already clearly defined, the key policies like broadcasting have strong community support, and the issues have volunteers ready and waiting to implement them. The only thing preventing us from fixing these bugs quickly and permanently is that we're blocked on finalizing a few key policy decisions.

I believe the most effective use of our time is to unblock the community. If the maintainer team can reach a final decision on these three key issues, it would unleash all of that volunteer energy:

  • Broadcasting policy (#8159): Fortunately, this isn't a long-term project; implementing the full policy is a small, simple fix, based around just two features. The mult(), div(), and rem() methods already broadcast scalars. The only methods requiring an immediate update are add() and sub(); I've confirmed that the logic for those can be adapted directly from the simple loop already in mult() (changing *= to += or -=). Beyond that, the only other change is throwing Friendly Error Messages in unsupported cases. My review of 100 sketches shows zero disruption from these changes. In this case, the permanent solution is also the quick fix. Approving it is an easy win.

  • The x/y/z behavior (#8153): This is a simple bug fix that aligns x, y, and z with the correct behavior of toString(). The exact changes that need to be made have been identified, and involve removing a few characters of code. It's also backed by previous discussion of true 1D/2D vector support.

  • The dimensions naming (#8155): We've already refined the API through several rounds of feedback, identified the core problems, and proposed a standard, precedent-backed solution. The final step is just a quick cross-check against existing getter/setter patterns across the library (which I can complete by tomorrow) to ensure long-term consistency.

Once these policies are approved, the community can immediately start implementing permanent fixes for these NaN bugs, and many other issues will be unblocked (e.g. #8188, #8189, #8210, #8215, and #8218).

What do you think of this approach? It seems like a way to get the best of both worlds: a zero-debt immediate fix (the README) and a clear plan to rapidly unlock the permanent fixes.

@ksen0
Copy link
Member

ksen0 commented Oct 29, 2025

Hi @GregStanton thank you for your thoughtful review! After going through this with @davepagurek , here are my takeaways and recommendation for this PR:

  1. This PR should be updated to be more focused on only the zero-argument usage, in a way that does not introduce new partial broadcasting policy. See also this comment describing @davepagurek 's proposal of "a zero vector that still has a fixed size, like other vectors, but with the specification of that size deferred until it can be inferred." In other words, this solution would only affect zero-arg vectors, and not by affecting broadcasting, but rather by allowing the existence of a vector of unspecified size until first usage. This PR should include the documentation note that this usage is not recommended; as well as the unit tests and FES note on zero-arg use. @perminder-17 does that all sound reasonable? Thanks so much for working on this!

  2. This implementation (as was noted) has drawbacks, but on discussion and reflection of the various linked issues, I think it is sensible for two related reasons. First, it is in the spirit of p5.js to support as little friction as possible in common uses; I think there is enough usage of createVector() (including in Nature of Code) that it is worthwhile to reduce this friction. Second, as all other Vector proposals suggest (or don't contradict), zero-arg usage of createVector() should be marked for deprecation and not supported at all in the future. However, given that it is currently supported, and there was not wide consensus to deprecate it, the proposed patch for minimum backwards compatibility seems justifiable. If there are severe drawbacks I am not seeing, I am very happy to reconsider this, but I think as long as there are unit tests (to reduce developer confusion) and FES messages reminding not to use createVector() (to reduce user confusion) - it is alright.

  3. Although [2.0] Proposal: A standard policy for vector dimension mismatches #8159 is leaning toward no partial broadcasting (and it's a great discussion that I'm really glad is happening!) there is not yet clear and wide consensus (besides that issue, there are other places where there was more support for partial broadcasting). Also, as far as I understand, the standard broadcasting solution to this situation is to disallow zero-arg use (and update docs accordingly) - I agree overall with future deprecation, however, it has not been deprecated (or at minimum, there has not been - as far as I know - meaningful deliberation and consensus in the past to fully deprecate zero-arg usage). Again, if I'm missing something please let me know, I tried to catch up as best as I could.

While considering this I also referred to Dan Shiffman's comment and from what I could tell, although the code examples in that comment won't work with this patch, the teaching approach does use constructors with explicit arguments, so this supports keeping the scope of this fix separate from broadcasting fix.

Thanks all for your careful work on this!

@GregStanton
Copy link
Collaborator

GregStanton commented Oct 29, 2025

Hi @ksen0 and @davepagurek,

Thank you both for digging into this so thoroughly and proposing the size-deferred approach. I really appreciate the effort to find a solution that minimizes friction for users migrating from 1.x, especially those working from major references like Nature of Code. I absolutely share the goal of making this transition as smooth as possible.

However, after analyzing the size-deferment proposal in more detail, I'm increasingly concerned that this specific automatic fix might inadvertently introduce more friction and complexity than it eliminates, both for users and contributors. While I've tried to remain open to different automatic solutions, my analysis keeps leading back to the conclusion that the simpler approach (guiding users via README + FES message) might actually be the most user-friendly one in this specific case.

Concerns about the size-deferment approach

While extremely clever, this approach seems to introduce several significant drawbacks:

  1. It creates deep user confusion: The core issue is that this patch introduces complex, "magic" behavior (implicitly mutable dimensions, hidden states, automatic resizing) specifically for users who follow the createVector() pattern. Instead of guiding them towards the clear 2.x model, it makes their code harder to understand and debug, inviting invisible bugs. As @sidwellr pointed out, users lose a clear mental model ("what dimension is my vector?"), violating the principle of least astonishment.

  2. It is entangled with, and conflicts with, broadcasting: While the intent is to disentangle this PR from broadcasting, @sidwellr's v1.add(v2) example, where v2 is temporarily a 1D vector, perfectly illustrates the unavoidable conflict. Does the size-deferred vector v2 get "locked" by padding the vector itself with zeros as an implicit side effect, or does broadcasting repeat the x value without side effects? The two systems collide, forcing an arbitrary rule and undermining the clarity the broadcasting proposal (#8159) aims to achieve.

  3. It harms contributor accessibility: This is critical for the project's health. The size-deferment patch obfuscates the fundamental concept of dimension, making ongoing stabilization work more difficult. Adding complex, state-dependent logic (_setDimension, _matchDimensions, etc.) to potentially every vector operation creates another significant barrier for contributors. Looking forward, ensuring interoperability with future classes like Matrix is also complicated by this edge-case fix. Overall, it risks making a core class unmaintainable and complicates ongoing work on many other critical, open issues.

  4. Unaddressed edge cases & inconsistencies: Interactions with unary operations that don't imply a dimension (limit(), mag(), magSq(), normalize(), and setMag()), or methods like .setValue(), have not been specified. These entail additional special cases and new inconsistencies. For example, .setValue() currently throws an error if the user tries to set a z-value on a 1D vector, whereas .z simply does nothing in this case. This is an existing inconsistency that I'm working to resolve. Size-deferment adds a third inconsistency, whereby out-of-bounds elements can be set, and the start of a vector is padded with zeros. This adds complexity, when our overall goal is simplification.

Reconsidering the friction

The primary motivation seems to be avoiding the friction of users manually updating createVector() calls, particularly in those widespread Nature of Code examples. However, I believe the friction introduced by the size-deferment approach is ultimately much higher and more persistent:

  • The ongoing friction of debugging silent failures caused by implicit, "magic" dimension changes.
  • The ongoing friction of trying to understand and predict this complex, undocumented behavior.
  • The ongoing friction for contributors facing a more complex and brittle codebase.

In contrast, the README + FES message approach involves a simple, one-time, explicitly-guided code change. It leverages the excellent capabilities of the Friendly Error System, turning a potentially breaking error into a helpful, in-context tutorial, guiding the user directly to the simple fix. While it requires a manual update, this transparency respects users and leads to clearer code.

Crucially, since the zero-argument createVector() will likely be deprecated anyway, users will eventually need to update their code regardless. With the 2.0 stabilization period extending until August 2026, the FES message provides ample lead time for this simple, guided update. This feels like the lower-friction path, minimizing pain for both users and contributors.

Recommendation

Given the significant drawbacks, I strongly believe the size-deferment approach, while well-intentioned as a temporary bridge, is ultimately detrimental due to the persistent complexity it introduces.

Could we reconsider the simpler path?

  1. Implement the README note + FES message. This provides clear, actionable guidance with zero technical debt, making the fix easy to understand and implement, using p5's excellent FES capabilities.

  2. Finalize the core policies (#8159, #8153, #8155). This unblocks the community and allows for permanent, stable fixes using simple, well-understood code. The case for the broadcasting proposal (#8159), in particular, is now exceptionally strong. Every major math, machine-learning, and creative-coding library that has encountered this problem has come to the same conclusion. It represents a simple, low-risk implementation path, making it a quick win once approved. The implementation is also exceptionally simple.

This approach seems to offer the best balance: it addresses the immediate compatibility concern clearly and effectively, without compromising the long-term stability, simplicity, and maintainability of p5.Vector. It acknowledges the disruption of the breaking change but argues that a quick, guided fix is less harmful than introducing stubborn, hidden complexity.

What are your thoughts on this analysis? I'm eager to find the best solution together.

@sidwellr
Copy link

I think the simplest, lowest-impact solution to fix 1.x sketches that break today is to replace the body of Vector.sub() with the body of Vector.add() and change the + to -. This will fix the NaN issue (#8117), and then most users will not notice that some 3D vectors turn up in their code since they will only be using v.x and v.y to get the coordinates they need for drawing. It's basically the "3D illusion" that existed in version 1 and frankly, if that bug didn't exist I doubt if issues #8117 or #8118 (which led to #8149 and its sub-issues) would have been created.

But the issues we are discussing under #8149 are important and I'm glad we are having the discussions. This simple fix does not address the larger issue of vector dimension mismatches (#8159), so is a quick fix until that is resolved. Making Vector.add() and Vector.sub() consistent should make that effort easier. And this only addresses the last bullet in @ksen0's actionable solution; it doesn't deprecate createVector() with no arguments or create a friendly warning/suggestion when it is used. See @GregStanton's comment above under the heading A truly quick fix for how to do these.

@GregStanton
Copy link
Collaborator

GregStanton commented Oct 30, 2025

Hi @ksen0 and @davepagurek. Thanks, @sidwellr, for that insightful analysis! You've hit on a crucial insight: the NaN bug in sub() is a simple, targeted fix. This perfectly highlights the high-leverage opportunity we have right now.

The permanent fix is just as quick as the temporary fix

Fixing this the temporary way (making sub() match add()) is the same amount of work as fixing this the permanent way (making sub() match mult()).

The full, permanent solution isn't a long-term project. It's an extremely simple change:

  1. For add() and sub(), we adapt the simple for loop already in mult(), changing *= to += or -=.
  2. For all other mismatches, we add a simple else condition to throw a Friendly Error Message.

This implementation is just as fast as the temporary patch, but it has the massive benefit of also unblocking all the other stabilization work for div(), set(), etc., and it solves the problem correctly, once and for all.

The case for the permanent fix

As we've discussed in the broadcasting proposal (#8159), the non-standard pad-by-zero logic creates internal inconsistency and confusion:

  • add(2) adds 2 only to the first element (non-standard)
  • mult(2) multiplies every element by 2 (standard)

My careful review of 100 sketches (50 for add(), 50 for mult()) shows the nonstandard pad-by-zero behavior is relied on by 0% of users. In contrast, the standard broadcasting behavior in mult() is common and aligns with user expectations. The standard behavior is both useful and universal: openFrameworks uses it for addition, as does every other major library, across domains and languages.

Seizing the opportunity

Given that the permanent fix is just as fast, has 0% disruption, and aligns with a universal standard that is the only mismatch policy our users actually rely on, it seems to be the most efficient path forward.

Why spend time on a temporary patch that is no faster to implement, implements a 0%-used confusing behavior, and will need to be undone?

Could we seize this opportunity to implement the quick, permanent fix by approving #8159 and patching sub? This respects both the urgency of the bug and the long-term health of the library.

@sidwellr
Copy link

I agree with @GregStanton that making sub() match mult() will fix the NaN issue. But it is also a breaking change from version 1 and needs to be documented as such. I don't think that a sample size of 100 sketches is statistically significant to say this won't break existing sketches, but I also doubt that many users use v.add(2) to add 2 to v.x; it would be more common (and readable) to use v.x += 2.

So, I don't object to this proposal, but observe that it is not "just as quick" because it would require updating documentation as well as code, and also note it isn't a complete fix; #8159 also requires checking for mismatched dimensions. Do we have enough support for #8159 to approve and implement it?

@GregStanton
Copy link
Collaborator

GregStanton commented Oct 31, 2025

Thanks, @sidwellr, for your feedback and attention to detail!

The good news is that the simplicity of the broadcasting proposal means the fix itself is extremely simple, including the documentation, which would need to be updated in either case (it's currently incomplete, in various ways).

I'll address the excellent points you raised.

Sample-size concerns: Abundance of evidence indicates zero disruption

I totally agree that we need to be cautious about breaking changes, and I'm glad you questioned the sample size. It's a critical point, which is why I combined (1) the empirical data from the sketches with (2) strong domain knowledge.

  • Empirical data: My review of 100 sketches (50 for add(), 50 for mult()) found 0% relied on the non-standard behavior.
  • Domain Knowledge: This 0% finding is what we'd expect. The non-standard behavior is not documented by any reference examples, it has a more writable and readable alternative (v.x += 2), it has no clear use cases, and it is inconsistent with every single major library, across languages and domains—even Processing's PVector does not behave this way.
A more rigorous, Bayesian analysis (for the curious)

For those interested in statistical rigor, this is a textbook case for a beta-binomial model. Given the $0$ observed uses in our $n=50$ sample for add(), and a very generous prior belief that maybe 1 in 1,000 sketches that use add() rely on the nonstandard behavior (0.1%), we can be 97.5% confident that the true usage rate in the wild is, at most, 0.351% (or about 1 in 284 sketches that use add()). It's likely that the true proportion is significantly lower and may well be exactly zero.

This change simplifies documentation

You're also right that this requires a documentation change—thankfully, it's a simplification! The 2.x docs need a significant update anyway. For example, most of the mult() docs are missing.

Adopting standard broadcasting makes the documentation easier because the rules are simpler, they reflect actual usage, and they make the API consistent. Instead of the complicated and incomplete documentation that we have now, we can provide documentation that is so consistent, it's essentially a matter of copying and pasting:

mult():

Multiplies vectors with the same number of elements: createVector(1, 2).mult(2, 3) returns a vector with elements [2, 6]. If a vector is multiplied by a single element like 2, all its elements will be multiplied by 2.

add():

Adds vectors with the same number of elements: createVector(1, 2).add(2, 3) returns a vector with elements [3, 5]. If a vector is added with a single element like 2, all its elements will be added with 2.

And yes, we would absolutely add this to the compatibility README.

The full fix is simple, fast, and removes flawed code

The permanent fix (patching add()/sub() and adding error checks across methods) is extremely simple and more performant.

Simpler Code: The entire implementation is just adapting the simple for loop from mult() and adding an else { FES-error } clause. The error message is simple because the behavior is simple: "The number of elements provided must either be one, or the same as the number of elements in the calling vector."

Better Performance: The current add()/sub() implementations contain unnecessary checks on every single element; these erroneous checks are there because the current system is harder to reason about. Standard broadcasting allows us to delete this flawed code, simplifying the implementation and removing a small performance overhead.

So, the permanent fix is quick to implement (it's just a copy/paste), easy to document, and results in cleaner, faster-running code. This seems like a clear win-win, allowing us to fix the NaN bug permanently and correctly, without adding any significant implementation cost.

Given this, could we seize this opportunity by approving #8159? This would unblock the community to implement the permanent fix right away and seems like the most efficient path forward for everyone.

@ksen0
Copy link
Member

ksen0 commented Oct 31, 2025

Hi all! Thanks for your attention on all this and the various vector topics. Maintainers (@limzykenneth , @perminder-17 , @dave , and I) discussed this and the related threads. I hope that you can understand that we would like to keep this PR narrowly focused on a compatibility fix (not on changing broadcasting policy) to reduce friction migrating 1.x → 2.x.

We're not quite ready to support the standard broadcasting proposal in 2.1. This PR is the last code change in that milestone. We are happy to review all the discussion for standard broadcasting for 2.2 (or any next 2.1.x patches), but for this minor release, it is too last-minute. All those proposals include substantial refactoring; and to the best of my understanding, the proposed fix is not creating the backwards compatibility we're discussing.

Planned scope of this PR, which should "address" rather than "resolve" the issue (plus tests):

  • Mark zero-arg createVector() as deprecated and emit warnings (FES).
  • Add a size-deferral check that sets a vector’s dimensionality on first use and emits the deprecation message — this does not change vector operation logic or implement partial broadcasting.

I also acknowledge @sidwellr’s and @GregStanton's suggestions (adopting the behaviour of add or mult everywhere) as a reasonable targeted options, but these also address broadcasting policy, which we'd like to have more time to consider. For now, we prefer to keep any changes in this PR to address usage of zero-argument vectors only. We then can finish discussion and adopt broadcasting policy with less of a rush for a later release (2.2 or any patch before that) given the 2.1 timeline. I really hope this helps to clarify the reasoning.

@GregStanton
Copy link
Collaborator

Thanks, @ksen0, for the update on the maintainers' discussion.

This is a significant change in direction from the other proposals. I need to take some time to think through the technical implications of the size-deferment approach and how it will impact the 15+ stabilization issues I'm stewarding and the volunteers working on them.

I'll post a more detailed follow-up once I've had a chance to fully assess the situation, so that we can align on how to handle this.

@Ayaan005-sudo
Copy link

Thanks @GregStanton for pointing me to this PR — really helpful.

I’ve now understood how createVector() is being handled here. The fallback logic ensures that if no arguments are passed, it doesn’t break downstream functions like screenToWorld(). Instead of returning NaN or undefined behavior, it pads the vector with a sensible Z value using the matrix, which keeps the conversion stable.

Still trying to fully understand the rest of the code and your points — learning a lot as I go.

@Ayaan005-sudo
Copy link

Hey @GregStanton , I wanted to revisit the original flow we were working through around createVector() and rotate().

Initially, we were focused on how users pass arguments to createVector() — whether it's 1D, 2D, or 3D — and how that affects rotation. In issue #8153, we discussed manual dimension detection, and then in #8155, we moved to using the shape API. The idea was:

  • 1D vectors should throw an error
  • 2D and 3D vectors should rotate correctly
  • And if no arguments are passed to createVector(), we still needed a solution — which was pending

Now I see that in PR #8203, Perminder added a fallback inside screenToWorld() that pads Z from the matrix if the vector is 2D. That makes sense for screen-to-world conversion, but I’m confused whether this helps with the original rotate(createVector()) case — especially when no arguments are passed.

Does this fallback logic apply to rotate() as well, or is that still unresolved? Just trying to understand if I’ve missed something or if the no-argument case for rotate() still needs a fix.

Would really appreciate your help clarifying this!

@sidwellr
Copy link

sidwellr commented Nov 4, 2025

@ksen0 Thanks for the focus. Although this won't fix the NaN bug in sub() (which will still include NaN when the subtrahend has more elements than the minuend), I agree this will address the issue for sketches that encounter the issue because they use zero-arg createVector(). This should also resolve issue #8117. The NaN bug should be fixed by #8159 when a dimension compatibility check is added.

@ksen0
Copy link
Member

ksen0 commented Nov 4, 2025

Hi again everyone! Update from the last few days: after @GregStanton and @davepagurek had a chance to go through it in more detail, and then @limzykenneth and @perminder-17 and I considered their recommendations, we figured out that the most reasonable way forward is to focus this PR even further, to only be the FES message warning on createVector() with zero args (with "addresses" not "fixes" for the issue). This would allow at least a bit more of an understandable behavior in 2.1, so we can go ahead with the release.

Since there is not a clear consensus on this patch approach specifically, and also not on the broadcasting (and other vector topics) generally, we also want to make more space for the dialogue that seems to be needed. There's still proposals for a very targeted patch for backwards compatibility, but this will not be part of this PR, and will be proposed as a separate PR for discussion and consideration (after 2.1). Some of the discussions have had to take place over Discord or in calls, out of practical necessity. I have tried to provide updates, but we'd really like to make these kinds of API decision making conversations more inclusive of everyone who wants to be a part of them. So after 2.1 I will post invites for contributors interested in these discussions (the ones on Vector and Matrix API topics) to join a call (or potentially a discord channel). If you've commented on any of the issues about those topics, I will tag you there! No need to take action, just a heads up.

Thanks again for all the careful discussion so far.

@GregStanton
Copy link
Collaborator

GregStanton commented Nov 4, 2025

Hi @ksen0, this is fantastic news! I'm 100% on board with this new direction.

Focusing this PR only on the createVector() no-arg issue is the perfect, clean solution, and I'm especially thrilled about the plan to create a more inclusive, public call for the bigger policy discussions. I feel this is a huge step forward.

I have one critical clarification on the implementation, to ensure we minimize user confusion.

"Warning" (Bug Remains) vs. "Error" (Bug Fixed)

Your comment mentions an "FES message warning." My concern is that this approach has two major problems:

  1. It doesn't fix the technical bug: If createVector() just logs a warning but still returns a [0,0,0] vector, it will not fix the NaN bug. The bug is vec2D.sub(zero3D). If createVector() still produces that zero3D, the user's sketch will still fail silently with NaN.
  2. It's a confusing user experience: A "warning" creates a mixed message. It tells the user, "You can do this, but don't," which is ambiguous.

The fix I proposed was for createVector() with no arguments to throw a friendly FES error. This is a crucial distinction that solves both problems:

  1. An error is the fix: createVector() throws an FES error, which stops the code and permanently prevents the NaN bug.
  2. An error is clearer: It sends an unambiguous message: "This no longer works, and here's how you can fix it." A perfect FES message would be:

"In 1.x, createVector() was a shortcut for createVector(0, 0, 0). In 2.x, p5.js has vectors of any dimension, so you must provide your desired number of zeros. Use createVector(0, 0) for a 2D vector and createVector(0, 0, 0) for a 3D vector."

Why This "Error" Approach is Safe (The NoC Data)

I know the main hesitation for throwing a "hard" error has been the (very valid) fear of breaking Nature of Code.

To address this, I did a full audit of the NoC site and its linked sketches. The great news is that the disruption is tiny and easily manageable:

  1. The no-arg pattern is in only 1 video and 2 code sketches.
  2. The text in 6 chapters can be fixed with a simple search-and-replace.

This is a localized issue that can be fixed at the source in under an hour. Given this data, we can safely use the FES error. It's the simplest, clearest, and most p5-esque solution: it permanently fixes the bug and provides a helpful, in-context tutorial.

Question

Is this the implementation the team is planning?

@ksen0
Copy link
Member

ksen0 commented Nov 4, 2025

Great question! I'll try to clarify:

  1. Indeed, @davepagurek has shared the suggested message: "In 1.x, createVector() was a shortcut for createVector(0, 0, 0). In 2.x, p5.js has vectors of any dimension, so you must provide your desired number of zeros. Use createVector(0, 0) for a 2D vector and createVector(0, 0, 0) for a 3D vector." This will be used.
  2. The outcome of the discussion is that there is no actual fix to the bug to be implemented in 2.1. You are right that the issue will not be fixed by this PR, because there was no consensus. This is not the goal of the FES warning.
  3. The goal of the FES warning is to help the user, instead of alarming and mysterious failure. The message is not ambiguous and helps the user address it. A warning that explains what went wrong is a better user experience than nothing.

While the list of NoC materials (thanks for providing that, I'll follow up on it! It is helpful to check if existing materials can be updated) is not long, that is by far not the only such materials. If I understand correctly, the safety argument is that "this pattern is not used very much." Whether or not the search-based sampling method (from the other empirical analysis) is representative, I would like to clarify that when I bring up specific examples (like NoC), it is to argue that "this pattern is used sometimes". I am not making an argument related to volume, but about presence in teaching materials, which can have substantial impact even in relatively smaller scales.

After all the discussion, I do still believe there is a technical solution that addresses all the various needs, including supporting this established (even if infrequent) pattern. However, it is clear that the time constraint related to 2.1 is not helpful here. So the proposed implementation will be in another PR, open to discussion (there and over Discord). Then, if that doesn't actually work out to meet all the different requirements, we can consider to elevate the warning to an error.

Let me know if there's any other concerns?

@GregStanton
Copy link
Collaborator

GregStanton commented Nov 4, 2025

Thank you so much @Ayaan005-sudo for taking a look at the current PR and sharing your question. This discussion has moved quickly, so I'll share some updates that I hope will answer your question.

The fallback logic you described is no longer the approach that we are going to take. The discussion on this PR ruled it out as problematic. The new approach is much simpler. It's centered around an FES message, rather than updating logic. We're still ironing out the last detail. I've proposed it can throw a helpful FES error message (which stops the code and helps users fix the problem), rather than a warning (which would let the code continue, creating a [0, 0, 0] vector, and leading to a NaN bug.)

This "error" approach makes your fixes to rotate() possible: The no-argument createVector() would be removed, guiding users to fix their code at the source (e.g., to createVector(0, 0)). This means you wouldn't have to write any complex, "magic" logic to guess the user's intent. You could trust that you will only receive vectors with clear, user-specified dimensions (1D, 2D, or 3D), making your implementation clean and robust.

This error approach will also unblock bug fixes from other volunteers including @justAnotherAnotherUser and @reshma045, who have been assigned to fix heading() and setHeading(). Their fixes depend on being able to reliably distinguish a 2D vector from a 3D one. Throwing an FES error to remove the ambiguous "no-arg" case is the only way to make their bug fixes possible.

If you want to share your thoughts on this "error vs. warning" discussion (especially how it impacts your rotate() fix), your feedback would be incredibly valuable!

Update: I posted this before I noticed the most recent reply from @ksen0. I'll summarize the key decisions here. The decision for 2.1 is to continue with an FES warning for now. The error fix and other core policies (like broadcasting, x/y/z, etc.) will be discussed in a new, inclusive forum (a call or a Discord channel). If you've commented on an issue relating to p5.Vector or p5.Matrix, you'll be invited. We'll need to wait for that discussion to be finalized before we can move forward with implementation. Thanks for your patience!

@Ayaan005-sudo
Copy link

Thanks @GregStanton — I support the warning approach for now. It’s the safest option for users and for contributors like me working on downstream fixes. It prevents silent bugs from [0, 0, 0] vectors and gives us a clean base to build on.

I feel confident about my rotate() fix flow now. Here's the structure I’ve been working with:

  • Users call createVector(...)
  • If no arguments are passed, the FES warning blocks execution early
  • If arguments are passed, I check the dimension:
    • 1D → throw error
    • 2D/3D → rotate allowed
  • This lets me avoid fallback logic and keep the implementation clean

Before I start coding, I wanted to confirm: should I go ahead and implement this now, even though issues #8153 (manual dimension detection) and #8155 (shape API) aren’t finalized yet? Or would it be better to write a temporary patch and refactor later once those are resolved?

Appreciate your guidance!

@GregStanton
Copy link
Collaborator

GregStanton commented Nov 5, 2025

Hi @Ayaan005-sudo! Thank you so much for following up. This is a super confusing situation, so I'm really glad you're asking for clarification. Your analysis is 100% correct, except that when you expressed support for an approach, you accidentally used the wrong label.

Correction: You supported the error approach

Here is the short version: The approach you supported is the error approach, not the warning approach. I also advocated for this, but the situation is complex. Project leadership decided to hold off on throwing an error for now.

Current plan: The warning approach

To clarify the current plan (from @ksen0's last message):

  • The 2.1 fix will only be an FES warning.
  • This warning will not stop the code.
  • This means createVector() with no arguments will still create an ambiguous [0,0,0] vector, and the resulting NaN bug will still happen.

Here's a summary of the broader discussion, to clear up any remaining confusion:

  1. The FES error message (which I've been proposing) is the one that would stop the code, prevent the NaN bug, and give you a clean, simple implementation path for rotate().
  2. The warning approach does not prevent the NaN bug, and it does not allow you to correctly identify the intended dimension of the vectors passed to rotate().

Where this leaves you

Your analysis of the rotate() flow is 100% correct, but it's based on the error approach, not the warning approach. Given the maintainers' current warning-not-error plan, your rotate() function will still get an ambiguous [0,0,0] vector (the user might intend it to be 2D, for example).

My advice is to please hold off on implementation for now.

It pains me to ask you to pause when you're ready to go, but it would pain me even more to see you have to write complex, "magic" logic to handle a buggy, ambiguous vector that we are all trying to remove permanently. It's not clear how that would work, and if you manage to do it, you'd just have to rewrite it later.

What you can do now: Join the new forum

A plan was announced by @ksen0, the p5 lead, to hold a call (or set up a Discord channel) to help us work through some nuances of the p5.Vector and p5.Matrix issues. You'll be invited! So, the best thing you can do for now is to keep an eye out for that invite, so that you can share your perspective.

Thanks again for your patience. I'm doing my best to get these core issues unblocked so you and the other volunteers can get back to building!

@Ayaan005-sudo
Copy link

@GregStanton
Just wanted to check — where will the invite for the upcoming call or Discord channel be shared?

I’d love to join and contribute to the discussion around p5.Vector and p5.Matrix. If possible, please share the invite with me directly or let me know where to look for it.

Thanks so much!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[2.0] Stabilize behavior of createVector() with zero arguments

6 participants